--- kind: error_handling name: MicroPython C扩展与Python层错误处理:OSError/ValueError、异常捕获与启动容错 category: error_handling scope: - '**' source_files: - esp32s3/firmware/builtins/machine_i2c_extmod.c - esp32s3/firmware/builtins/machine_i2c.c - esp32s3/firmware/builtins/machine_pin.c - firmware/port/builtins/modmachine.c - esp32s3/firmware/boards/labplus_Ledong_v2/modules/_boot.py - esp32s3/firmware/boards/labplus_Ledong_v2/modules/mpython.py - esp32s3/firmware/boards/labplus_Ledong_v2/modules/educore/mpython_ble/advertising/__init__.py - docs/examples/network/http_server.py - docs/examples/network/tcpClient.py - docs/examples/network/udp_client.py - docs/examples/network/时钟/AnalogClock.py - docs/examples/network/时钟/DigitalClock.py --- # 1. 整体方案 本仓库基于 MicroPython for ESP32(ESP32 port)与 ESP-IDF v5.x,错误处理分为两个层级: - **C 层(编译进固件)**:位于 `firmware/port/builtins/`(ESP32 板型)和 `esp32s3/firmware/builtins/`(ESP32-S3 板型),通过 MicroPython 的 `mp_raise_*` API 抛出 Python 异常。 - **Python 层(运行时加载)**:位于 `firmware/port/modules/`、`esp32s3/firmware/boards/*/modules/` 以及 `docs/examples/`,使用 `try/except` 捕获 `OSError`、`ValueError` 等标准异常,部分模块还自定义了 Exception 子类。 项目没有统一的错误码枚举或中间件式错误处理器;错误以“底层 C 抛异常 → 上层 Python try/except 捕获”的方式自底向上传播。 # 2. C 层错误模型 ## 2.1 使用的 MicroPython 异常类型 - `mp_raise_OSError(-ret)`:用于 I/O 类错误,将 ESP-IDF 返回的负 errno 转换为 Python `OSError`。典型位置:`esp32s3/firmware/builtins/machine_i2c_extmod.c` 中所有 I2C 读写失败路径(如 `machine_i2c_start`、`machine_i2c_stop`、`machine_i2c_readinto`、`machine_i2c_write`、`machine_i2c_readfrom`、`machine_i2c_writeto`、`machine_i2c_writevto` 等)。这是 I2C 子系统的统一错误出口。 - `mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("..."))`:用于语义性错误,如 `machine_i2c_init`/`start`/`stop`/`read`/`write` 在对应函数指针为 NULL 时抛出 `OSError("I2C operation not supported")`。 - `mp_raise_ValueError(...)` / `mp_raise_msg_varg(&mp_type_ValueError, ...)`:用于参数校验错误,如 `machine_pin.c` 中的 `invalid pin`、`pin can only be input`、`bad wake value`、`no resources`、`invalid pin for wake`;`machine_i2c.c` 中的 `I2C(%d) doesn't exist`;`modmachine.c` 中 `machine_freq` 对非允许频率抛出 `frequency must be 20MHz, 40MHz, 80Mhz, 160MHz or 240MHz`。 - `mp_raise_TypeError` / `mp_raise_RuntimeError`:在 `modmachine.c` 的 `machine_sleep_helper` 中,当触摸唤醒配置失败时抛出 `RuntimeError("esp_sleep_enable_touchpad_wakeup() failed")`。 ## 2.2 错误来源映射 | C 文件 | 主要职责 | 典型错误 | |---|---|---| | `esp32s3/firmware/builtins/machine_i2c_extmod.c` | 通用 machine.I2C 协议绑定 | I2C 操作不支持、硬件返回负 errno | | `esp32s3/firmware/builtins/machine_i2c.c` | 硬件 I2C 实例化与参数校验 | 无效 I2C id | | `esp32s3/firmware/builtins/machine_pin.c` | GPIO Pin 构造与 irq/wake 配置 | 无效引脚、模式不兼容、唤醒资源冲突 | | `firmware/port/builtins/modmachine.c` | 系统级功能(freq/sleep/reset/unique_id) | 非法频率、睡眠唤醒失败、软复位 | ## 2.3 重启与恢复 - `machine_reset()`:调用 `esp_restart()` 直接硬重启。 - `machine_soft_reset()`:设置 `pyexec_system_exit = PYEXEC_FORCED_EXIT` 并抛出 `SystemExit`,实现 MicroPython 解释器级软复位。 - `machine_reset_cause()`:将 ESP-IDF 的 `esp_reset_reason()` 映射到 `MP_PWRON_RESET`、`MP_WDT_RESET`、`MP_DEEPSLEEP_RESET`、`MP_HARD_RESET`、`MP_SOFT_RESET` 等常量,供 Python 层诊断重启原因。 # 3. Python 层错误处理约定 ## 3.1 启动阶段容错(_boot.py) `esp32s3/firmware/boards/labplus_Ledong_v2/modules/_boot.py` 展示了典型的启动容错模式: - 挂载文件系统失败时捕获 `OSError`,回退到 `inisetup.setup()` 重建 VFS。 - 获取机器型号、MAC 地址等操作均用 `try/except` 包裹,失败时打印 `Unknown machine` / `Unknown mac` 而非中断启动。 - 上电后立即关闭 RGB LED 防止随机灯亮,属于“先初始化再尝试业务”的容错策略。 ## 3.2 传感器/I2C 设备封装层的重试 + 明确异常 `esp32s3/firmware/boards/labplus_Ledong_v2/modules/mpython.py` 中磁力计/加速度计相关代码呈现固定模式: - 每个 I2C 读/写序列外层套 `retry < 5` 的循环。 - 内部用裸 `try:` 捕获所有异常(包括 I2C 底层抛出的 `OSError`),失败则 `retry += 1`。 - 重试耗尽后 `raise Exception("i2c read/write error!")`,抛出一个**无具体类型的通用 `Exception`**,而非 `OSError` 或自定义异常类。该模式出现在 `_get_raw`、`_get_temperature`、`_get_id`、`_judge_id` 等多个方法中。 ## 3.3 BLE 自定义异常 `esp32s3/firmware/boards/labplus_Ledong_v2/modules/educore/mpython_ble/advertising/__init__.py` 定义了 `class BLEError(Exception):`,是仓库中唯一观察到的自定义异常类,用于 BLE 子系统。 ## 3.4 示例代码中的网络错误处理 `docs/examples/network/` 下的示例(`http_server.py`、`tcpClient.py`、`udp_client.py`、`AnalogClock.py`、`DigitalClock.py` 等)普遍采用 `try: ... except OSError:` 的模式捕获网络 I/O 错误,体现教程代码对 `OSError` 作为网络错误的约定。 # 4. 架构与分层约定 | 层级 | 位置 | 错误方式 | 说明 | |---|---|---|---| | 硬件驱动层(C) | `esp32s3/firmware/builtins/*.c` | `mp_raise_OSError` / `mp_raise_ValueError` | 严格遵循 MicroPython 异常体系,错误信息通过 `MP_ERROR_TEXT` 提供 | | 系统接口层(C) | `firmware/port/builtins/modmachine.c` | `mp_raise_*` + 重启常量 | 暴露 `reset_cause`、`soft_reset`、`sleep` 等系统能力 | | 板级 Python 模块 | `esp32s3/firmware/boards/*/modules/mpython.py` | `try/except` + 重试 + `raise Exception(...)` | 对底层 I2C 做重试包装,但顶层仍抛通用 `Exception` | | 启动脚本 | `*_boot.py` | `try/except OSError` | 保证启动流程健壮,失败时降级 | | 用户示例 | `docs/examples/*` | `try/except OSError` | 教学示例统一捕获 `OSError` | # 5. 约束与规范(从代码行为归纳) 1. **C 层禁止返回错误码给 Python**:所有底层 I2C/PIN 操作失败都通过 `mp_raise_*` 立即转为 Python 异常,调用方无需检查返回值。 2. **I2C 错误一律映射为 `OSError`**:`machine_i2c_extmod.c` 中所有负返回值统一经 `mp_raise_OSError(-ret)` 转换,使 Python 层可用 `except OSError` 统一处理。 3. **参数校验错误使用 `ValueError`**:无效引脚、非法频率、非法唤醒值等均抛 `ValueError`,便于区分“参数错误”和“运行时 I/O 错误”。 4. **启动阶段必须容忍异常**:`_boot.py` 对所有可能失败的初始化步骤加 `try/except`,确保即使某项服务不可用也能继续启动。 5. **传感器封装层采用“重试 + 通用 Exception”模式**:`mpython.py` 中 I2C 访问失败最多重试 5 次,之后抛出无类型信息的 `Exception("i2c read/write error!")`——这是一种简化设计,未定义专用异常类型。 6. **重启原因通过常量暴露**:`modmachine.c` 将 ESP-IDF 重启原因映射为 `MP_PWRON_RESET`、`MP_WDT_RESET`、`MP_DEEPSLEEP_RESET`、`MP_HARD_RESET`、`MP_SOFT_RESET` 五个整数常量,供 Python 层 `machine.reset_cause()` 查询。 7. **BLE 子系统有独立异常命名空间**:`advertising/__init__.py` 中 `BLEError` 是唯一自定义异常类,表明 BLE 模块有意隔离其错误域。 # 6. 关键文件 - `esp32s3/firmware/builtins/machine_i2c_extmod.c` — I2C 协议层错误统一出口 - `esp32s3/firmware/builtins/machine_i2c.c` — I2C 实例化参数校验 - `esp32s3/firmware/builtins/machine_pin.c` — GPIO 引脚与唤醒配置错误 - `firmware/port/builtins/modmachine.c` — 系统重启、睡眠、频率等错误与重启原因 - `esp32s3/firmware/boards/labplus_Ledong_v2/modules/_boot.py` — 启动容错模板 - `esp32s3/firmware/boards/labplus_Ledong_v2/modules/mpython.py` — 传感器 I2C 重试与通用 Exception 模式 - `esp32s3/firmware/boards/labplus_Ledong_v2/modules/educore/mpython_ble/advertising/__init__.py` — BLE 自定义异常 `BLEError` - `docs/examples/network/http_server.py`、`tcpClient.py`、`udp_client.py`、`AnalogClock.py`、`DigitalClock.py` — 示例层 `try/except OSError` 惯例